package com.derbysoft.ota.application.service.impl;

import java.util.HashMap;
import java.util.Map;

import com.derbysoft.ota.application.util.Elements;
import com.derbysoft.ota.interfaces.protocol._2009a.*;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

import com.derbysoft.ota.application.service.StagingService;
import com.derbysoft.ota.application.util.Beans;
import com.derbysoft.ota.application.util.Jaxb2Marshallers;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.googlecode.functionalcollections.FunctionalIterables;

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

public class StagingServiceImpl implements StagingService {

	private Jaxb2Marshaller jaxb2Marshaller;
	
	public StagingServiceImpl(Jaxb2Marshaller jaxb2Marshaller) {
		this.jaxb2Marshaller = jaxb2Marshaller;
	}

	@Override
	public OTAHotelAvailRS getAvail(OTAHotelAvailRQ availRQ) {
		return verifyAndGet(availRQ, avails(), OTAHotelAvailRS.class);
	}

	private Map<OTAHotelAvailRQ, OTAHotelAvailRS> avails() {
		Map<OTAHotelAvailRQ, OTAHotelAvailRS> avails = new HashMap<OTAHotelAvailRQ, OTAHotelAvailRS>();
		avails.put(
			unmarshall("OTA_HotelAvailRQ1.xml", OTAHotelAvailRQ.class),
			unmarshall("OTA_HotelAvailRS1.xml", OTAHotelAvailRS.class)
		);
		avails.put(
			unmarshall("OTA_HotelAvailRQ2.xml", OTAHotelAvailRQ.class),
			unmarshall("OTA_HotelAvailRS2.xml", OTAHotelAvailRS.class)
		);
		avails.put(
			unmarshall("OTA_HotelAvailRQ3.xml", OTAHotelAvailRQ.class),
			unmarshall("OTA_HotelAvailRS3.xml", OTAHotelAvailRS.class)
		);
		return avails;
	}

	public OTAHotelSearchRS search(OTAHotelSearchRQ searchRQ) {
		return verifyAndGet(searchRQ, searchPairs(), OTAHotelSearchRS.class);
	}

	private Map<OTAHotelSearchRQ, OTAHotelSearchRS> searchPairs() {
		Map<OTAHotelSearchRQ, OTAHotelSearchRS> pairs = new HashMap<OTAHotelSearchRQ, OTAHotelSearchRS>();
		pairs.put(
			unmarshall("OTA_HotelSearchRQ1.xml", OTAHotelSearchRQ.class),
			unmarshall("OTA_HotelSearchRS1.xml", OTAHotelSearchRS.class)
		);
		pairs.put(
			unmarshall("OTA_HotelSearchRQ2.xml", OTAHotelSearchRQ.class),
			unmarshall("OTA_HotelSearchRS2.xml", OTAHotelSearchRS.class)
		);
		pairs.put(
			unmarshall("OTA_HotelSearchRQ3.xml", OTAHotelSearchRQ.class),
			unmarshall("OTA_HotelSearchRS3.xml", OTAHotelSearchRS.class)
		);
		pairs.put(
			unmarshall("OTA_HotelSearchRQ4.xml", OTAHotelSearchRQ.class),
			unmarshall("OTA_HotelSearchRS4.xml", OTAHotelSearchRS.class)
		);
		return pairs;
	}

	@SuppressWarnings("unchecked")
	public JAXBElement<HotelResResponseType> make(JAXBElement<HotelResRequestType> requestObject) {
		HotelResResponseType response = verifyAndGet(requestObject, resPairs(), HotelResResponseType.class);
		return new JAXBElement<HotelResResponseType>(
			new QName(Elements.OTA_NAMESPACE_URI, "OTA_HotelResRS"),
			HotelResResponseType.class,
			response
		);
	}

	@SuppressWarnings("unchecked")
	private Map<JAXBElement<HotelResRequestType>, HotelResResponseType> resPairs() {
		Map<JAXBElement<HotelResRequestType>, HotelResResponseType> pairs =
				new HashMap<JAXBElement<HotelResRequestType>, HotelResResponseType>();
		pairs.put(
			unmarshall("OTA_HotelResRQ1.xml", JAXBElement.class),
			resValue("OTA_HotelResRS1.xml", HotelResResponseType.class)
		);
		pairs.put(
			unmarshall("OTA_HotelResRQ2.xml", JAXBElement.class),
			resValue("OTA_HotelResRS2.xml", HotelResResponseType.class)
		);
		pairs.put(
			unmarshall("OTA_HotelResRQ3.xml", JAXBElement.class),
			resValue("OTA_HotelResRS3.xml", HotelResResponseType.class)
		);
		pairs.put(
			unmarshall("OTA_HotelResRQ4.xml", JAXBElement.class),
			resValue("OTA_HotelResRS4.xml", HotelResResponseType.class)
		);
		pairs.put(
			unmarshall("OTA_HotelResRQ5.xml", JAXBElement.class),
			resValue("OTA_HotelResRS5.xml", HotelResResponseType.class)
		);
		return pairs;
	}

	@SuppressWarnings("unchecked")
	private <T> T resValue(String xml, Class<T> clazz) {
		JAXBElement element = unmarshall(xml, JAXBElement.class);
		return (T) element.getValue();
	}

	public OTACancelRS cancel(OTACancelRQ requestObject) {
		return verifyAndGet(requestObject, cancelPairs(), OTACancelRS.class);
	}

	private Map<OTACancelRQ, OTACancelRS> cancelPairs() {
		Map<OTACancelRQ, OTACancelRS> pairs = new HashMap<OTACancelRQ, OTACancelRS>();
		pairs.put(
			unmarshall("OTA_CancelRQ1.xml", OTACancelRQ.class),
			unmarshall("OTA_CancelRS1.xml", OTACancelRS.class)
		);
		pairs.put(
			unmarshall("OTA_CancelRQ2.xml", OTACancelRQ.class),
			unmarshall("OTA_CancelRS2.xml", OTACancelRS.class)
		);
		pairs.put(
			unmarshall("OTA_CancelRQ3.xml", OTACancelRQ.class),
			unmarshall("OTA_CancelRS3.xml", OTACancelRS.class)
		);
		return pairs;
	}




	@SuppressWarnings("unchecked")
	private <T> T verifyAndGet(Object rq, Map<?, ?> map, Class<T> rsType) {
		final String correlationID = correlationIDOf(rq);
		Object detected = FunctionalIterables.make(map.keySet()).detect(
				new Predicate<Object>() {
					public boolean apply(Object input) {
						return correlationIDOf(input).equals(correlationID);
					}
				});

		Preconditions.checkArgument(detected != null, "Invalid correlationID [" + correlationID + "]");
		verify(rq, detected);
		return (T) map.get(detected);
	}
	
	private String correlationIDOf(Object rq) {
		if (rq instanceof JAXBElement) {
			JAXBElement element = (JAXBElement) rq;
			return checkAndGetCorrelationID(element.getValue());
		}
		return checkAndGetCorrelationID(rq);
	}

	private String checkAndGetCorrelationID(Object rq) {
		return (String) Preconditions.checkNotNull(Beans.getProperty(rq, "correlationID"),  "CorrelationID required.");
	}

	private void verify(Object request, Object expected) {
		String marshalledRequest = marshall(request);
		String marshalledExpected = marshall(expected);
		Preconditions.checkArgument(
			marshalledRequest.equals(marshalledExpected), 
			"Invalid request [" + marshalledRequest + "], expected request is [" + marshalledExpected+ "]"
		);
	}

	private String marshall(Object request) {
		return Jaxb2Marshallers.marshall(jaxb2Marshaller, request);
	}

	private <T> T unmarshall(String xml, Class<T> expectedType) {
		return Jaxb2Marshallers.unmarshall("ota2009a/xml/staging/" + xml, expectedType);
	}
	

}
